﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Diagnostics;
using DifferenceEngine;
using Microsoft.Web.Administration;
using System.IO.Compression;
//using SevenZip;

namespace IISAutoDeployment
{
    public class Worker
    {
        #region Constructors
        public Worker()
        {
            ApplicationPools = _serverManager.ApplicationPools.ToList();
            Sites = _serverManager.Sites.ToList();
            _tempDir = new DirectoryInfo(Path.Combine(Path.GetTempPath(),Path.GetRandomFileName()));
            //_tempDir = new DirectoryInfo(Path.Combine(@"D:\Temp", Path.GetRandomFileName()));
        }
        
        public void RemoteWorker(string remoteNode)
        {
            _remoteManager = ServerManager.OpenRemote(remoteNode);
            RemoteApplicationPools = _serverManager.ApplicationPools.ToList();
            RemoteSites = _serverManager.Sites.ToList();
        }
        ~Worker()
        {
            if (_tempDir.Exists)
                _tempDir.Delete(true);
        }
        #endregion

        #region Public Methods

        /// <summary>
        /// Method encapsulating the various IIS related commands
        /// </summary>
        /// <param name="site">Target Site against which commands are run</param>
        /// <param name="command">The command to be run</param>
        /// <param name="backuplocation">Path to the backup directory for the Target Site</param>
        /// <param name="remoteNode">Secondary node FQN or IP</param>
        /// <returns>True if success; False if error</returns>
        public Boolean SiteCommand(string site, AllCommands.IisCommands command, string backuplocation = null, string remoteNode = null)
        {
            var selectedSite = String.IsNullOrEmpty(remoteNode) ? _serverManager.Sites[site] : _remoteManager.Sites[site];
            if (selectedSite == null)
                return false;
            var appPoolName = selectedSite.Applications[0].ApplicationPoolName;
            var applicationPool = String.IsNullOrEmpty(remoteNode) ? _serverManager.ApplicationPools.FirstOrDefault(x => x.Name == appPoolName) : 
                                                                    _remoteManager.ApplicationPools.FirstOrDefault(x => x.Name == appPoolName);
            var sitePath = selectedSite.Applications[0].VirtualDirectories[0].PhysicalPath;
            var remPath = sitePath.Replace(sitePath.Substring(0, 3), @"\\" + remoteNode + @"\" + sitePath.Substring(0, 1) + @"$\");
            var sitePhysicalPath = new DirectoryInfo(String.IsNullOrEmpty(remoteNode) ? sitePath : remPath );
            
            switch (command)
            {
                case AllCommands.IisCommands.Start:
                    AppPoolCommand(applicationPool, AllCommands.IisCommands.Start);
                    AppPoolCommand(applicationPool, AllCommands.IisCommands.Recycle);
                    selectedSite.Start();
                    break;
                case AllCommands.IisCommands.Stop:
                    selectedSite.Stop();
                    AppPoolCommand(applicationPool, AllCommands.IisCommands.Stop);
                    break;
                case AllCommands.IisCommands.Backup:
                    if (backuplocation == null)
                        return false;
                    var backPath = backuplocation.StartsWith(@"\\") ? backuplocation : 
                        backuplocation.Replace(backuplocation.Substring(0, 3), @"\\" + remoteNode + @"\" + backuplocation.Substring(0, 1) + @"$\");
                    BackupSite(sitePhysicalPath, new DirectoryInfo(String.IsNullOrEmpty(remoteNode) ? backuplocation : backPath));
                    break;
                case AllCommands.IisCommands.Deploy:
                    if (!_tempDir.GetDirectories().Any())
                        return false;
                    DeploySite(sitePhysicalPath);
                    break;
                case AllCommands.IisCommands.Dependencies:
                    return _dependencies.CopyBlbFiles(sitePhysicalPath);
                case AllCommands.IisCommands.Browse:
                    BrowseToSite(selectedSite.Bindings[0].Host);
                    break;
                case AllCommands.IisCommands.Skip:
                    return true;
            }
            CommitChanges();
            return true;
        }

        /// <summary>
        /// This unzips the deployment package to the temp dir specified by the TempDir DirectoryInfo
        /// </summary>
        /// <param name="deploymentPackage">Path to deployment package</param>
        /// <param name="runSanityCheck">Run check on basic web.config elements?</param>
        public Boolean Unzip(string deploymentPackage, string runSanityCheck)
        {
            try
            {
                ZipFile.ExtractToDirectory(deploymentPackage, _tempDir.FullName);
                if (runSanityCheck.Contains("True")) SanityCheck();
            }
            catch (Exception e)
            {
                MessageBox.Show(e.ToString());
                return false;
            }
            return true;
        }

        /// <summary>
        /// Runs the diff against the backup and deployment web.configs
        /// </summary>
        /// <param name="backuplocation">Path to backup location for target site</param>
        /// <returns>The DiffViewResults containing the diff results; Null if failure</returns>
        public DiffViewResults TextDiff(string backuplocation)
        {
            if (backuplocation == null || !_tempDir.GetDirectories().FirstOrDefault().GetFiles().Any())
                return null;
            var backupWebConfig =
                new DirectoryInfo(backuplocation).GetFiles().FirstOrDefault(x => String.Compare(x.Name, "web.config", true) == 0);
            if (backupWebConfig == null)
                return new DiffViewResults(null, null, 0, null);
            var deployWebConfig =
                _tempDir.GetDirectories().FirstOrDefault().GetFiles().FirstOrDefault(x => String.Compare(x.Name, "web.config", true) == 0);
            
            DiffListTextFile sLf;
            DiffListTextFile dLf;
            try
            {
                sLf = new DiffListTextFile(backupWebConfig.FullName);
                dLf = new DiffListTextFile(deployWebConfig.FullName);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "File Error");
                return null;
            }

            try
            {
                var de = new DiffEngine();
                var time = de.ProcessDiff(sLf, dLf, DiffEngineLevel.Medium);

                var rep = de.DiffReport();
                return new DiffViewResults(sLf, dLf, time, rep);
                //var dlg = new DiffResultsWindow(sLf, dLf, rep, time);
                //dlg.ShowDialog();
            }
            catch (Exception ex)
            {
                var tmp = string.Format("{0}{1}{1}***STACK***{1}{2}",
                    ex.Message,
                    Environment.NewLine,
                    ex.StackTrace);
                MessageBox.Show(tmp, "Compare Error");
                return null;
            }
        }
        #endregion

        #region Helper methods
        /// <summary>
        /// Runs a check on basic web.config items such as ConnectionsStrings to ensure correctness before deploy
        /// </summary>
        /// <remarks>Depreciated in favor of web.config diff engine</remarks>
        public void SanityCheck()
        {
            var folder = _tempDir.GetDirectories().FirstOrDefault();
            if (folder == null) return;

            var webConfig = folder.GetFiles().FirstOrDefault(x => String.Compare(x.Name, "web.config", true) == 0);
            if ( (folder.Name.Contains("ProdExternal")) ||
                ((folder.Name.Contains("External") || folder.Name.Contains("external")) &&
                !(folder.Name.Contains("Staging") || folder.Name.Contains("staging")) && !(folder.Name.Contains("Tst") || folder.Name.Contains("tst"))))
            {
                using (var reader = new StreamReader(webConfig.FullName))
                {
                    var lnNum = 0;
                    while (!reader.EndOfStream)
                    {
                        var p = reader.ReadLine();
                        lnNum++;
                        if (p.Contains("connectionStrings"))
                        {
                            while (true)
                            {
                                p = reader.ReadLine();
                                lnNum++;
                                if (!p.Contains("</connectionStrings>")) break;
                                if (p.Contains("<!--")) continue;
                                if (p.Contains("connectionString"))
                                {
                                    if (!p.Contains("api.dmz"))
                                    {
                                        if (p.Contains("db03") || p.Contains("DB03")) break;
                                        throw new Exception("Does not contain correct connectionString line: " + lnNum);
                                    }
                                }
                            }
                            continue;
                        }
                        //if (p.Contains("someOtherParameter"))
                        //{
                            
                        //}
                    }
                }               
            }
        }
        private static void BrowseToSite(string site)
        {
            Process.Start("iexplore", site);
        }

        private void AppPoolCommand(ApplicationPool applicationPool, AllCommands.IisCommands command)
        {
            if (applicationPool == null)
            {
                MessageBox.Show("Cannot find appPool for selected site");
                return;
            }
            switch (command)
            {
                case AllCommands.IisCommands.Recycle:
                    applicationPool.Recycle();
                    break;
                case AllCommands.IisCommands.Restart:
                    AppPoolCommand(applicationPool, AllCommands.IisCommands.Stop);
                    applicationPool.Start();
                    break;
                case AllCommands.IisCommands.Start:
                    if (applicationPool.State == ObjectState.Started)
                        break;
                    applicationPool.Start();
                    break;
                case AllCommands.IisCommands.Stop:
                    if (applicationPool.State == ObjectState.Stopped)
                        break;
                    applicationPool.Stop();
                    break;
            }
            CommitChanges();
        }

        private void CommitChanges()
        {
            try
            {
                _serverManager.CommitChanges();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());   
            }
        }

        private static void BackupSite(DirectoryInfo selectedSite, DirectoryInfo backupLocation)
        {
            DirectoryCopy(selectedSite, backupLocation, true);
        }

        private void DeploySite(DirectoryInfo sitePhysicalPath)
        {
            // Keep the current App_Data folder
            var dirs = sitePhysicalPath.GetDirectories();
            foreach (var directoryInfo in dirs.Where(directoryInfo => !directoryInfo.Name.Contains("App_Data")))
            {
                directoryInfo.Delete(true);
            }
            var deploymentFiles = _tempDir.GetDirectories().FirstOrDefault();
            if (deploymentFiles == null)
                return;
            DirectoryCopy(deploymentFiles,sitePhysicalPath,true);
            //_tempDir.Delete(true);
        }

        private static void DirectoryCopy(DirectoryInfo dir, DirectoryInfo destDirName, bool copySubDirs)
        {
            // Get the subdirectories for the specified directory.
            if (!dir.Exists)
                throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + dir);
            
            // Skip App_data
            if (destDirName.Name.Contains("App_Data"))
                return;

            // If the destination directory doesn't exist, create it. 
            if (!destDirName.Exists)
                destDirName.Create();

            // Get the files in the directory and copy them to the new location.
            dir.EnumerateFiles().ToList().ForEach(file => file.CopyTo(Path.Combine(destDirName.FullName, file.Name), true));
            
            // If copying subdirectories, copy them and their contents to new location. 
            if (!copySubDirs) return;
            dir.EnumerateDirectories().ToList().ForEach(subdir => DirectoryCopy(subdir, new DirectoryInfo(Path.Combine(destDirName.FullName, subdir.Name)), true));
        }
        #endregion

        #region Properties
        private readonly DirectoryInfo _tempDir; 
        private readonly ServerManager _serverManager = new ServerManager();
        private ServerManager _remoteManager;
        private readonly Dependencies _dependencies = new Dependencies();
        public List<Site> Sites;
        public List<ApplicationPool> ApplicationPools;
        public List<Site> RemoteSites;
        public List<ApplicationPool> RemoteApplicationPools;

        public static class AllCommands
        {
            public enum IisCommands
            {
                Start,
                Stop,
                Restart,
                Recycle,
                Backup,
                Deploy,
                Null,
                Browse,
                Diff,
                Dependencies,
                Skip
            }

            public static IisCommands Parse(string command)
            {
                IisCommands result;
                return Enum.TryParse(command, out result) ? result : IisCommands.Null;
            }
        }
        #endregion
    }
}
